package com.kvn.doc;

import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;

import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.ImportDeclaration;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.body.BodyDeclaration;
import com.github.javaparser.ast.body.FieldDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.Parameter;
import com.github.javaparser.ast.body.TypeDeclaration;
import com.github.javaparser.ast.expr.AnnotationExpr;
import com.github.javaparser.ast.expr.MemberValuePair;
import com.github.javaparser.ast.expr.NormalAnnotationExpr;
import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr;
import com.github.javaparser.ast.type.ClassOrInterfaceType;
import com.github.javaparser.ast.type.PrimitiveType;
import com.github.javaparser.ast.type.ReferenceType;
import com.github.javaparser.ast.type.Type;
import com.github.javaparser.ast.type.VoidType;
import com.kvn.doc.export.ConsoleExporter;
import com.kvn.doc.model.FieldSignature;
import com.kvn.doc.model.MvcMethodSignature;
import com.kvn.doc.tool.ItemFilter;
import com.kvn.doc.tool.ListUtil;

/**
 * @author wzy
 * @date 2017年11月6日 上午10:37:17
 */
public class AstTool {
	private static List<MvcMethodSignature> mvcMethodLs = new ArrayList<>();
	private static String sourceAbsolutePath = "E:/gitWorkspace/kvn-wealth/kvn-usage/src/main/java/";
	private static String fileRelativePath = "com/cn/kvn/usage/controller/ParamProcessController.java";

	public static void main(String[] args) throws Exception {

		// creates an input stream for the file to be parsed
		FileInputStream in = new FileInputStream(sourceAbsolutePath + fileRelativePath);

		// parse it
		CompilationUnit cu = JavaParser.parse(in);
		TypeDeclaration typeDesc = cu.getTypes().get(0);
		String className = retriveClassName(cu, typeDesc);
		String classComment = retriveNodeComment(typeDesc);
		String rootPath = findMvcPath(typeDesc.getAnnotations());

		List<BodyDeclaration> bodyLs = typeDesc.getMembers();
		for (BodyDeclaration body : bodyLs) {
			if (!(body instanceof MethodDeclaration)) {
				continue;
			}

			MethodDeclaration md = (MethodDeclaration) body;
			AnnotationExpr anno = ListUtil.findOne(md.getAnnotations(), new ItemFilter<AnnotationExpr>() {
				@Override
				public boolean apply(AnnotationExpr input) {
					return "RequestMapping".equals(input.getName().getName());
				}
			});

			if (anno == null) {
				continue; // 方法上没有 @RequestMapping
			}

			String functionDesc = retriveNodeComment(md);
			String accessPath = rootPath + findMvcPath(md.getAnnotations());
			List<Parameter> params = md.getParameters();
			List<FieldSignature> paramDescLs = buildParameters(params);
			List<FieldSignature> returnDescLs = generateReturnDesc(md, cu);

			MvcMethodSignature mvcMethod = new MvcMethodSignature();
			mvcMethod.setFunctionDesc(functionDesc);
			mvcMethod.setAccessPath(accessPath);
			mvcMethod.setParamDescLs(paramDescLs);
			mvcMethod.setReturnDescLs(returnDescLs);
			mvcMethodLs.add(mvcMethod);
		}

		ConsoleExporter.getInstance().export(mvcMethodLs, System.out);
	}

	private static boolean isMvcMethod(MethodDeclaration md) {
		AnnotationExpr anno = ListUtil.findOne(md.getAnnotations(), new ItemFilter<AnnotationExpr>() {
			@Override
			public boolean apply(AnnotationExpr input) {
				return "RequestMapping".equals(input.getName().getName());
			}
		});
		return anno != null;
	}

	private static List<FieldSignature> generateReturnDesc(MethodDeclaration md, CompilationUnit cu) {
		Type rtnType = md.getType();

		if (rtnType instanceof PrimitiveType || isJavaType(rtnType)) {
			FieldSignature field = new FieldSignature();
			field.setFieldType(rtnType.toString());
			return Collections.singletonList(field);
		}

		if (rtnType instanceof VoidType) {
			return Collections.emptyList();
		}

		if (!(rtnType instanceof ReferenceType)) {
			throw new RuntimeException("暂不支持的类型：" + rtnType);
		}
		
		// 返回类型是引用类型
		Type type = ((ReferenceType) md.getType()).getType();
		if (!(type instanceof ClassOrInterfaceType)) {
			throw new RuntimeException("返回类型异常，不是Class or Interface");
		}

		ClassOrInterfaceType ciType = (ClassOrInterfaceType) type;
		String filePath = findClassFilePath(ciType, cu);
		if(filePath == null){ // jdk的类
			FieldSignature field = new FieldSignature();
			field.setFieldType(rtnType.toString());
			return Collections.singletonList(field);
		}
		CompilationUnit cu2 = loadCompilationUnit(filePath);
		return generateFiledsDesc(cu2);

	}

	private static boolean isJavaType(Type rtnType) {
		String typeName = rtnType.toString();
		return "String,BigDecimal".contains(typeName);
	}

	private static List<FieldSignature> generateFiledsDesc(CompilationUnit cu2) {
		List<FieldSignature> fieldLs = new ArrayList<>();

		List<BodyDeclaration> bdLs = cu2.getTypes().get(0).getMembers();
		for (BodyDeclaration bd : bdLs) {
			if (!(bd instanceof FieldDeclaration)) {
				continue;
			}

			FieldDeclaration fd = (FieldDeclaration) bd;
			FieldSignature field = new FieldSignature();
			field.setFieldName(fd.getVariables().get(0).getId().getName());
			field.setFieldType(fd.getType().toString());
			field.setDesc(fd.getComment() == null ? "" : fd.getComment().getContent());
			AnnotationExpr requiredAnno = ListUtil.findOne(fd.getAnnotations(), new ItemFilter<AnnotationExpr>() {
				@Override
				public boolean apply(AnnotationExpr input) {
					String annoName = input.getName().getName();
					return "NotNull".equals(annoName) || "NotBlank".equals(annoName);
				}
			});
			field.setRequired(requiredAnno != null);
			fieldLs.add(field);
		}
		return fieldLs;
	}

	private static CompilationUnit loadCompilationUnit(String filePath) {
		try {
			FileInputStream in = new FileInputStream(filePath);
			return JavaParser.parse(in);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 获取类的路径
	 * 
	 * @param ciType
	 * @param cu
	 * @return
	 */
	private static String findClassFilePath(ClassOrInterfaceType ciType, CompilationUnit cu) {
		// String prefix = new File("").getAbsolutePath() + "/src/main/java/";

		ListIterator<ImportDeclaration> idIt = cu.getImports().listIterator();
		while (idIt.hasNext()) {
			ImportDeclaration id = idIt.next();
			// 分在本包内，和本包外
			if (ciType.getName().equals(id.getName().getName())) {
				String classPath = id.getChildrenNodes().get(0).toString();
				if(classPath.startsWith("java.") || classPath.startsWith("javax.")){
					return null;
				}
				return sourceAbsolutePath + classPath.replace(".", "/") + ".java";
			}
		}

		// 没有找到，就是在当前包下
		String path = sourceAbsolutePath + cu.getPackage().getChildrenNodes().get(0).toString().replace(".", "/") + "/" + ciType.getName() + ".java";
		return path;
	}

	private static List<FieldSignature> buildParameters(List<Parameter> params) {
		List<FieldSignature> fieldLs = new ArrayList<>();
		for (Parameter param : params) {
			FieldSignature field = resolveMvcParam(param);
			fieldLs.add(field);
		}
		return fieldLs;
	}

	private static FieldSignature resolveMvcParam(Parameter param) {
		FieldSignature field = new FieldSignature();

		AnnotationExpr anno = ListUtil.findOne(param.getAnnotations(), new ItemFilter<AnnotationExpr>() {
			@Override
			public boolean apply(AnnotationExpr input) {
				return "RequestParam".equals(input.getName().getName());
			}
		});
		if (anno == null) {
			field.setFieldName(param.getId().getName());
		} else {
			SingleMemberAnnotationExpr smae = (SingleMemberAnnotationExpr) anno;
			field.setFieldName(smae.getMemberValue().toStringWithoutComments().replace("\"", ""));
		}

		AnnotationExpr requiredAnno = ListUtil.findOne(param.getAnnotations(), new ItemFilter<AnnotationExpr>() {
			@Override
			public boolean apply(AnnotationExpr input) {
				String annoName = input.getName().getName();
				return "NotNull".equals(annoName) || "NotBlank".equals(annoName);
			}
		});

		field.setRequired(requiredAnno != null);
		field.setFieldType(param.getType().toString());
		return field;
	}

	/**
	 * 找出springMvc的url
	 * 
	 * @param annotations
	 * @return
	 */
	private static String findMvcPath(List<AnnotationExpr> annos) {
		String path = "";
		for (AnnotationExpr anno : annos) {
			if (!"RequestMapping".equals(anno.getName().getName())) {
				continue;
			}
			if (anno instanceof SingleMemberAnnotationExpr) {
				SingleMemberAnnotationExpr smae = (SingleMemberAnnotationExpr) anno;
				path = smae.getMemberValue().toStringWithoutComments();
			}
			if(anno instanceof NormalAnnotationExpr){
				NormalAnnotationExpr na = (NormalAnnotationExpr) anno;
				List<MemberValuePair> memberLs = na.getPairs();
				MemberValuePair pair = ListUtil.findOne(na.getPairs(), new ItemFilter<MemberValuePair>() {
					@Override
					public boolean apply(MemberValuePair input) {
						return "value".equals(input.getName());
					}
				});
				path = pair.getValue().toString();
			}
		}
		return path.replace("\"", "");
	}

	/**
	 * 获取节点注释
	 * 
	 * @param node
	 * @return
	 */
	private static String retriveNodeComment(Node node) {
		if (node.getComment() == null) {
			return "";
		}
		String content = node.getComment().getContent();
		return content.replace("*", "");
	}

	private static String retriveClassName(CompilationUnit cu, TypeDeclaration typeDesc) {
		return cu.getPackage().getChildrenNodes().get(0) + typeDesc.getName();
	}

}
